#pragma once

#include <stx/core/stl_concepts.hpp>

// This is a simple, but functional, C++14 lambda library. It takes advantage of
// the fact that the standard <functional> header already provides std::bind()
// customization points (std::is_placeholder<>, std::is_bind_expression<>), and
// function objects such as std::plus(), std::greater(), std::logical_not(), and
// std::bit_xor(), corresponding to arithmetic, relational, logical and bitwise
// operators.

// This allows the library to provide a minimal implementation that still lets
// expressions such as (_1 + 5), (_1 % 2 == 0), (_1 > _2), or (_1 == ' ')
// to be composed and used as function objects.

// For example, (_1 + 5) is implemented as std::bind(std::plus<>, _1, 5).

// These "lambda" expressions can also be freely combined with std::bind. For
// example, std::bind(f, _1) == std::bind(g, _1) and std::bind(f, _1 + _2)
// both work and have the expected behavior.

inline namespace stx
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
using std::placeholders::_7;
using std::placeholders::_8;
using std::placeholders::_9;

template<typename T>
concept IsLambdaExpr__ = EXPR(!!std::is_placeholder_v<NoCvr<T>> ||
                              !!std::is_bind_expression_v<NoCvr<T>>);

#define UNARY_LAMBDA__(op, fn)                                       \
    [[nodiscard]] auto operator op(::stx::IsLambdaExpr__ auto&& arg) \
    {                                                                \
        return ::std::bind(fn{}, FORWARD(arg));                      \
    }                                                                \
                                                                     \
    static_assert(true)

#define POSTFIX_LAMBDA__(op, fn)                                          \
    [[nodiscard]] auto operator op(::stx::IsLambdaExpr__ auto&& arg, int) \
    {                                                                     \
        return ::std::bind(fn{}, FORWARD(arg));                           \
    }                                                                     \
                                                                          \
    static_assert(true)

#define BINARY_LAMBDA__(op, fn)                                         \
    template<::stx::IsNonVoid A, ::stx::IsNonVoid B>                    \
    requires EXPR(::stx::IsLambdaExpr__<A> || ::stx::IsLambdaExpr__<B>) \
    [[nodiscard]] auto operator op(A&& a, B&& b)                        \
    {                                                                   \
        return ::std::bind(fn{}, FORWARD(a), FORWARD(b));               \
    }                                                                   \
                                                                        \
    static_assert(true)

// standard
BINARY_LAMBDA__(+, std::plus<>);
BINARY_LAMBDA__(-, std::minus<>);
BINARY_LAMBDA__(*, std::multiplies<>);
BINARY_LAMBDA__(/, std::divides<>);
BINARY_LAMBDA__(%, std::modulus<>);
UNARY_LAMBDA__(-, std::negate<>);

BINARY_LAMBDA__(==, std::equal_to<>);
BINARY_LAMBDA__(!=, std::not_equal_to<>);
BINARY_LAMBDA__(>, std::greater<>);
BINARY_LAMBDA__(<, std::less<>);
BINARY_LAMBDA__(>=, std::greater_equal<>);
BINARY_LAMBDA__(<=, std::less_equal<>);

BINARY_LAMBDA__(&&, std::logical_and<>);
BINARY_LAMBDA__(||, std::logical_or<>);
UNARY_LAMBDA__(!, std::logical_not<>);

BINARY_LAMBDA__(&, std::bit_and<>);
BINARY_LAMBDA__(|, std::bit_or<>);
BINARY_LAMBDA__(^, std::bit_xor<>);
UNARY_LAMBDA__(~, std::bit_not<>);

namespace LambdaDetail__
{
#define UNARY_LAMBDA_FN__(op, fn)                   \
    struct fn final                                 \
    {                                               \
        decltype(auto) operator()(auto&& arg) const \
        {                                           \
            return op FORWARD(arg);                 \
        }                                           \
    }

#define POSTFIX_LAMBDA_FN__(op, fn)                 \
    struct fn final                                 \
    {                                               \
        decltype(auto) operator()(auto&& arg) const \
        {                                           \
            return FORWARD(arg) op;                 \
        }                                           \
    }

#define BINARY_LAMBDA_FN__(op, fn)                          \
    struct fn final                                         \
    {                                                       \
        decltype(auto) operator()(auto&& a, auto&& b) const \
        {                                                   \
            return FORWARD(a) op FORWARD(b);                \
        }                                                   \
    }

BINARY_LAMBDA_FN__(<<, left_shift);
BINARY_LAMBDA_FN__(>>, right_shift);

UNARY_LAMBDA_FN__(+, unary_plus);
UNARY_LAMBDA_FN__(*, dereference);

UNARY_LAMBDA_FN__(++, increment);
UNARY_LAMBDA_FN__(--, decrement);

POSTFIX_LAMBDA_FN__(++, postfix_increment);
POSTFIX_LAMBDA_FN__(--, postfix_decrement);

BINARY_LAMBDA_FN__(+=, plus_equal);
BINARY_LAMBDA_FN__(-=, minus_equal);
BINARY_LAMBDA_FN__(*=, multiplies_equal);
BINARY_LAMBDA_FN__(/=, divides_equal);
BINARY_LAMBDA_FN__(%=, modulus_equal);
BINARY_LAMBDA_FN__(&=, bit_and_equal);
BINARY_LAMBDA_FN__(|=, bit_or_equal);
BINARY_LAMBDA_FN__(^=, bit_xor_equal);
BINARY_LAMBDA_FN__(<<=, left_shift_equal);
BINARY_LAMBDA_FN__(>>=, right_shift_equal);
} // namespace LambdaDetail__

// additional
BINARY_LAMBDA__(<<, LambdaDetail__::left_shift);
BINARY_LAMBDA__(>>, LambdaDetail__::right_shift);

UNARY_LAMBDA__(+, LambdaDetail__::unary_plus);
UNARY_LAMBDA__(*, LambdaDetail__::dereference);

UNARY_LAMBDA__(++, LambdaDetail__::increment);
UNARY_LAMBDA__(--, LambdaDetail__::decrement);

POSTFIX_LAMBDA__(++, LambdaDetail__::postfix_increment);
POSTFIX_LAMBDA__(--, LambdaDetail__::postfix_decrement);

// compound assignment
BINARY_LAMBDA__(+=, LambdaDetail__::plus_equal);
BINARY_LAMBDA__(-=, LambdaDetail__::minus_equal);
BINARY_LAMBDA__(*=, LambdaDetail__::multiplies_equal);
BINARY_LAMBDA__(/=, LambdaDetail__::divides_equal);
BINARY_LAMBDA__(%=, LambdaDetail__::modulus_equal);
BINARY_LAMBDA__(&=, LambdaDetail__::bit_and_equal);
BINARY_LAMBDA__(|=, LambdaDetail__::bit_or_equal);
BINARY_LAMBDA__(^=, LambdaDetail__::bit_xor_equal);
BINARY_LAMBDA__(<<=, LambdaDetail__::left_shift_equal);
BINARY_LAMBDA__(>>=, LambdaDetail__::right_shift_equal);

/*
If fn is a non-member template function, or an overloaded function, using
`BIND(fn, ...)`, and if `fn` contains `,`, using `BIND((fn), ...)` instead;

Otherwise, using `std::bind()`. Especially, for a member template function,
using `std::bind(&A::template mem_fn, this, _1, 3, _2);`
*/
#define BIND(fn, ...)                                 \
    ::std::bind([&](auto&&... args) -> decltype(auto) \
                { return fn(FORWARD(args)...); } __VA_OPT__(, ) __VA_ARGS__)
} // namespace stx

// auto add(auto, auto, auto)
// {
//     return true;
// }

// struct A
// {
//     int f(auto n)
//     {
//         return n;
//     }
// };

// int main(void)
// {
//     std::bind(&A::template f<int>, A{}, 0);
//     auto a = A{};
//     a.f(8);

//     auto coll = "hello"s;
//     std::find_if(coll.begin(), coll.end(),
//         BIND((a.f), _1) || BIND((add), _1, 9, _1) || _1 == 'e');
// }